#include "dfs.h"
#include "dfs_inner.h"


/* static var */

#ifdef TAG
#undef TAG
#endif
#define TAG "DFS-FILE"


/* static func */





/* export func */

/**
 * @brief 打开文件
 * 
 * @param fd 文件打开后返回的文件句柄
 * @param path 文件路径
 * @param flags 
 * @return int 
 */
int dfs_file_open(struct dfs_fd *fd, const char *path, int flags)
{
    struct dfs_filesystem *fs;
    char *fullpath;
    int ret;

    if (fd == NULL)
    {
        LOG_E(TAG, "fd null");
        return -EINVAL;
    }

    fullpath = dfs_normalize_path(NULL, path);
    if (fullpath == NULL)
    {
        LOG_E(TAG, "fullpath create fail");
        dfs_set_errno(-ENOMEM);
        return -ENOMEM;
    }

    fs = dfs_fs_lookup(fullpath);
    if (fs == NULL)
    {
        LOG_E(TAG, "dfs file lookup '%s' fail", fullpath);
        dfs_mem_free(fullpath);
        dfs_set_errno(-ENOENT);
        return -ENOENT;
    }

    // LOG_D(TAG, "[%s]open file: %s",fs->ops->name, fullpath);
    fd->fs = fs;
    fd->fops = fs->ops->fops;
    fd->type = FT_REGULAR;
    fd->flags = flags;
    fd->size = 0;
    fd->pos = 0;
    fd->data = fs;

    if (!(fs->ops->flags & DFS_FS_FLAG_FULLPATH))
    {
        if (dfs_subdir(fs->path, fullpath) == NULL)
            fd->path = dfs_mem_strdup("/");
        else
            fd->path = dfs_mem_strdup(dfs_subdir(fs->path, fullpath));
        dfs_mem_free(fullpath);
        // LOG_I(TAG, "Actual file path: %s", fd->path);
    }
    else
    {
        fd->path = fullpath;
    }

    if (fd->fops->open == NULL)
    {
        dfs_mem_free(fd->path);
        fd->path = NULL;
        LOG_W(TAG, "ops undef open");
        dfs_set_errno(-ENOSYS);
        return -ENOSYS;
    }

    ret = fd->fops->open(fd);
    if (ret < 0)
    {
        dfs_mem_free(fd->path);
        fd->path = NULL;
        LOG_E(TAG, "%s open fail", fullpath);
        return ret;
    }

    fd->flags |= DFS_F_OPEN;
    if (fd->flags & O_DIRECTORY)
    {
        fd->type = FT_DIRECTORY;
        fd->flags |= DFS_F_DIRECTORY;
    }

    // LOG_I(TAG, "open ok");
    return 0;
}

/**
 * @brief 关闭文件
 * 
 * @param fd 
 * @return int 
 */
int dfs_file_close(struct dfs_fd *fd)
{
    int ret = 0;

    if (fd == NULL)
    {
        LOG_E(TAG, "fd null");
        return -ENXIO;
    }

    if (fd->fops->close)
    {
        ret = fd->fops->close(fd);
    }

    if (ret < 0) return ret;

    dfs_mem_free(fd->path);
    fd->path = NULL;

    return ret;
}

/**
 * @brief perform a io control on a file descriptor
 * 
 * @param fd 
 * @param cmd 
 * @param args 
 * @return int 
 */
int dfs_file_ioctl(struct dfs_fd *fd, int cmd, void *args)
{
    if (fd == NULL)
    {
        LOG_E(TAG, "fd null");
        return -EINVAL;
    }

    if (fd->type == FT_REGULAR)
    {
        switch (cmd)
        {
        case F_GETFL:
            return fd->flags;
            break;
        case F_SETFL:
            {
                int flags = (int)args;
                int mask = O_NONBLOCK | O_APPEND;
                flags &= mask;
                fd->flags &= ~mask;
                fd->flags |= flags;
            }
            break;
        default:
            break;
        }
    }

    if (fd->fops->ioctl)
    {
        return fd->fops->ioctl(fd, cmd, args);
    }

    return -ENOSYS;
}

/**
 * @brief 读取文件
 * 
 * @param fd 
 * @param buf 
 * @param len 
 * @return int 
 */
int dfs_file_read(struct dfs_fd *fd, void *buf, size_t len)
{
    int ret = 0;

    if (fd == NULL) return -ENOSYS;

    if (fd->fops->read == NULL)
    {
        LOG_E(TAG, "fops undef read");
        return -ENOSYS;
    }

    ret = fd->fops->read(fd, buf, len);
    if (ret < 0)
    {
        fd->flags |= DFS_F_EOF;
    }

    return ret;
}

/**
 * @brief fetch directory entries from a directory descriptor.
 * 
 * @param fd 
 * @param dirp 
 * @param nbytes 
 * @return int 
 */
int dfs_file_getdents(struct dfs_fd *fd, struct dirent *dirp, size_t bytes)
{
    if (fd == NULL || fd->type != FT_DIRECTORY)
    {
        LOG_E(TAG, "fd or type err");
        return -EINVAL;
    }

    if (fd->fops->getdents == NULL)
    {
        LOG_E(TAG, "fd undef getdents");
        return -ENOSYS;
    }

    return fd->fops->getdents(fd, dirp, bytes);
}

/**
 * @brief 删除文件
 * 
 * @param path 
 * @return int 
 */
int dfs_file_unlink(const char *path)
{
    int result;
    char *fullpath;
    struct dfs_filesystem *fs;

    /* Make sure we have an absolute path */
    fullpath = dfs_normalize_path(NULL, path);
    if (fullpath == NULL)
    {
        return -EINVAL;
    }

    /* get filesystem */
    if ((fs = dfs_fs_lookup(fullpath)) == NULL)
    {
        result = -ENOENT;
        goto __exit;
    }

    /* Check whether file is already open */
    if (dfs_fd_is_open(fullpath) == 0)
    {
        result = -EBUSY;
        goto __exit;
    }

    if (fs->ops->unlink != NULL)
    {
        if (!(fs->ops->flags & DFS_FS_FLAG_FULLPATH))
        {
            if (dfs_subdir(fs->path, fullpath) == NULL)
                result = fs->ops->unlink(fs, "/");
            else
                result = fs->ops->unlink(fs, dfs_subdir(fs->path, fullpath));
        }
        else
            result = fs->ops->unlink(fs, fullpath);
    }
    else result = -ENOSYS;

__exit:
    dfs_mem_free(fullpath);
    return result;
}

/**
 * @brief 写文件
 * 
 * @param fd 
 * @param buf 
 * @param len 
 * @return int 
 */
int dfs_file_write(struct dfs_fd *fd, const void *buf, size_t len)
{
    if (fd == NULL)
    {
        LOG_E(TAG, "fd null");
        return -EINVAL;
    }

    if (fd->fops->write == NULL)
    {
        LOG_E(TAG, "ops undef write");
        return -ENOSYS;
    }

    return fd->fops->write(fd, buf, len);
}

/**
 * @brief 刷新文件
 * 
 * @param fd 
 * @return int 
 */
int dfs_file_flush(struct dfs_fd *fd)
{
    if (fd == NULL)
    {
        LOG_E(TAG, "fd null");
        return -EINVAL;
    }

    if (fd->fops->flush == NULL)
    {
        LOG_E(TAG, "ops undef flush");
        return -ENOSYS;
    }

    return fd->fops->flush(fd);
}

/**
 * @brief 文件设置偏移位置
 * 
 * @param fd 
 * @param offset 
 * @return int 
 */
int dfs_file_lseek(struct dfs_fd *fd, off_t offset)
{
    int result;

    if (fd == NULL)
    {
        LOG_E(TAG, "fd null");
        return -EINVAL;
    }

    if (fd->fops->lseek == NULL)
    {
        LOG_E(TAG, "ops undef lseek");
        return -ENOSYS;
    }

    result = fd->fops->lseek(fd, offset);

    /* update current position */
    if (result >= 0) fd->pos = result;

    return result;
}

/**
 * @brief 获取文件信息
 * 
 * @param path 
 * @param buf 
 * @return int 
 */
int dfs_file_stat(const char *path, struct stat *buf)
{
    int result;
    char *fullpath;
    struct dfs_filesystem *fs;

    fullpath = dfs_normalize_path(NULL, path);
    if (fullpath == NULL)
    {
        return -1;
    }

    if ((fs = dfs_fs_lookup(fullpath)) == NULL)
    {
        LOG_E(TAG, "can't find mounted filesystem on this path:%s", fullpath);
        dfs_mem_free(fullpath);
        return -ENOENT;
    }

    if ((fullpath[0] == '/' && fullpath[1] == '\0') ||
        (dfs_subdir(fs->path, fullpath) == NULL))
    {
        /* it's the root directory */
        buf->st_dev   = 0;
        buf->st_mode  = S_IRUSR | S_IRGRP | S_IROTH |
                        S_IWUSR | S_IWGRP | S_IWOTH;
        buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
        buf->st_size    = 0;
        buf->st_mtime   = 0;
        
        dfs_mem_free(fullpath);

        return 0;
    }
    else
    {
        if (fs->ops->stat == NULL)
        {
            dfs_mem_free(fullpath);
            LOG_E(TAG, "the filesystem didn't implement this function");
            return -ENOSYS;
        }

        /* get the real file path and get file stat */
        if (fs->ops->flags & DFS_FS_FLAG_FULLPATH)
            result = fs->ops->stat(fs, fullpath, buf);
        else
            result = fs->ops->stat(fs, dfs_subdir(fs->path, fullpath), buf);
    }

    dfs_mem_free(fullpath);

    return result;
}

/**
 * @brief 文件重命名
 * 
 * @param oldpath 
 * @param newpath 
 * @return int 
 */
int dfs_file_rename(const char *oldpath, const char *newpath)
{
    int result;
    struct dfs_filesystem *oldfs, *newfs;
    char *oldfullpath, *newfullpath;

    result = 0;
    oldfullpath = dfs_normalize_path(NULL, oldpath);
    newfullpath = dfs_normalize_path(NULL, newpath);
    if (oldfullpath == NULL || newfullpath == NULL)
    {
        result = -ENOENT;
        goto __exit;
    }

    oldfs = dfs_fs_lookup(oldfullpath);
    newfs = dfs_fs_lookup(newfullpath);
    if (oldfs == newfs)
    {
        if (oldfs->ops->rename == NULL)
        {
            result = -ENOSYS;
        }
        else
        {
            if (oldfs->ops->flags & DFS_FS_FLAG_FULLPATH)
                result = oldfs->ops->rename(oldfs, oldfullpath, newfullpath);
            else
                /* use sub directory to rename in file system */
                result = oldfs->ops->rename(oldfs,
                                            dfs_subdir(oldfs->path, oldfullpath),
                                            dfs_subdir(newfs->path, newfullpath));
        }
    }
    else
    {
        result = -EXDEV;
    }

__exit:
    dfs_mem_free(oldfullpath);
    dfs_mem_free(newfullpath);

    /* not at same file system, return EXDEV */
    return result;
}

/**
 * @brief cause the regular file referenced by fd 
 * to be truncated to a size of precisely length bytes.
 * 
 * @param fd 
 * @param length 
 * @return int 
 */
int dfs_file_ftruncate(struct dfs_fd *fd, off_t length)
{
    int result;

    /* fd is null or not a regular file system fd, or length is invalid */
    if (fd == NULL || fd->type != FT_REGULAR || length < 0)
        return -EINVAL;

    if (fd->fops->ioctl == NULL)
        return -ENOSYS;

    result = fd->fops->ioctl(fd, DFS_FIOFTRUNCATE, (void*)&length);

    /* update current size */
    if (result == 0)
        fd->size = length;

    return result;
}


